package gu.sql2java;

import java.util.LinkedHashSet;

import gu.sql2java.exception.RuntimeDaoException;

import static gu.sql2java.SimpleLog.*;

/** 
 * container for multiple listener management
 * @author guyadong 
 */
public class ListenerContainer <B> implements TableListener<B> {
	protected final LinkedHashSet<TableListener<B>> listeners = new LinkedHashSet<TableListener<B>>(16);

	protected final boolean trace;

	/** Fire type required */
	private final FireType fireType;
	/** who fire listener */
	private static final ThreadLocal<FireType> FIRER = new ThreadLocal<FireType>(); 
	public ListenerContainer() {
		this(false, null);
	}

	public ListenerContainer(boolean trace, FireType fireType) {
		super();
		this.trace = trace;
		this.fireType = FireType.nullToLocal(fireType);
	}

	@Override
	public void beforeInsert(B bean)throws RuntimeDaoException{
		if(isReqFirer()){
			synchronized (listeners) {
				for(TableListener<B> listener:listeners){
					try{
						listener.beforeInsert(bean);
					}catch(Exception e){
						log("beforeInsert listener %s error:%s",listener.getClass().getName(),e.getMessage());
						if(trace){
							log(e);
						}
					}
				}
			}
		}
	}

	@Override
	public void afterInsert(final B bean)throws RuntimeDaoException{
		if(isReqFirer()){
			synchronized (listeners) {
				for(final TableListener<B> listener:listeners){
					try{
						listener.afterInsert(bean);
					}catch(Exception e){
						log("afterInsert listener %s error:%s",listener.getClass().getName(),e.getMessage());
						if(trace){
							log(e);
						}
					}			
				}
			}
		}
	}

	@Override
	public void beforeUpdate(B bean)throws RuntimeDaoException{
		if(isReqFirer()){
			synchronized (listeners) {
				for(TableListener<B> listener:listeners){
					try{
						listener.beforeUpdate(bean);
					}catch(Exception e){
						log("beforeUpdate listener %s error:%s",listener.getClass().getName(),e.getMessage());
						if(trace){
							log(e);
						}
					}
				}
			}
		}
	}

	@Override
	public void afterUpdate(final B bean)throws RuntimeDaoException{
		if(isReqFirer()){
			synchronized (listeners) {
				for(final TableListener<B> listener:listeners){
					try{
						listener.afterUpdate(bean);
					}catch(Exception e){
						log("afterUpdate listener %s error:%s",listener.getClass().getName(),e.getMessage());
						if(trace){
							log(e);
						}
					}
				}
			}
		}
	}

	@Override
	public void beforeDelete(B bean)throws RuntimeDaoException{
		if(isReqFirer()){
			synchronized (listeners) {
				for(TableListener<B> listener:listeners){
					try{
						listener.beforeDelete(bean);
					}catch(Exception e){
						log("beforeDelete listener %s error:%s",listener.getClass().getName(),e.getMessage());
						if(trace){
							log(e);
						}
					}
				}
			}
		}
	}

	@Override
	public void afterDelete(final B bean)throws RuntimeDaoException{
		if(isReqFirer()){
			synchronized (listeners) {
				for(final TableListener<B> listener:listeners){
					try{
						listener.afterDelete(bean);
					}catch(Exception e){
						log("afterDelete listener %s error:%s",listener.getClass().getName(),e.getMessage());
						if(trace){
							log(e);
						}
					}

				}
			}
		}
	}

	@Override
	public void done()throws RuntimeDaoException{
		if(isReqFirer()){
			synchronized (listeners) {
				for(final TableListener<B> listener:listeners){
					try{
						listener.done();
					}catch(Exception e){
						log("done listener %s error:%s",listener.getClass().getName(),e.getMessage());
						if(trace){
							log(e);
						}
					}
				}
			}
		}
	}
	/**
	 * determine if the container is empty.
	 * @return 
	 */
	public boolean isEmpty() {
		return listeners.isEmpty();
	}
	/**
	 * determine if the {@code listener} be added.
	 * @param listener
	 * @return {@code true} if {@code listener} exists in container
	 */
	public boolean contains(TableListener<B> listener) {
		synchronized (listeners) {
			return listeners.contains(listener);
		}
	}
	/**
	 * add {@code listener} into container
	 * @return {@code true} if add successfully.
	 */
	public boolean add(TableListener<B> listener) {
		synchronized (listeners) {
			if(null != listener && !listeners.contains(listener)){
				return listeners.add(listener);
			}
			return false;
		}
	}
	/**
	 * remove {@code listener} from container
	 * @param listener instance that will be removed.
	 * @return {@code true} if remove successfully.
	 */
	public boolean remove(TableListener<B> listener) {
		synchronized (listeners) {
			return null == listener? false : listeners.remove(listener);
		}
	}
	/** remove all listeners in container */
	public void clear() {
		synchronized (listeners) {
			listeners.clear();
		}
	}
	public void firer(FireType firer)	{
		FIRER.set(firer);
	}
	public void clearFirer()	{
		FIRER.remove();
	}

	protected boolean isReqFirer(){
		return FireType.nullToLocal(FIRER.get()).equals(fireType);
	}
	protected boolean isLocalReqFirer(){
		FireType type = FireType.nullToLocal(FIRER.get());
		return FireType.LOCAL.equals(type) && type.equals(fireType);
	}
	/**
	 * type of fire listener source:LOCAL,ROW_OBSERVER,default:LOCAL OR null
	 * @author guyadong
	 *
	 */
	public static enum FireType{
		/** DEFAULT:fire listener from local code which operate database */LOCAL,
		/** Subscribe from message queue(ACTIVEMQ/REDIS) which notify table UPDATE/INSERT/DELETE message,and fire listener  */ROW_OBSERVER;
		public static final FireType valueOrNull(String name){
			return name == null ? null : valueOf(name);
		}
		public static final FireType nullToLocal(FireType fireType){
			return fireType == null ? LOCAL : fireType;
		}
	}
}